package com.springboot.utils;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.stereotype.Service;

import java.nio.charset.StandardCharsets;

/**
 * Redis分布式锁工具类
 * 基于Redis实现的分布式锁，支持重试机制和原子性操作
 * 
 * @author miao
 * @Slf4j 注解提供日志功能
 * @Service 注解标识这是一个服务类
 */
@Slf4j
@Service
public class RedisDistributedLock {
    
    /**
     * Redis模板，用于执行Redis操作
     */
    @Autowired
    RedisTemplate<String, Object> template;

    /**
     * 锁的超时时间（毫秒）
     */
    private static final long TIMEOUT_MILLIS = 60000;

    /**
     * 获取锁的重试次数
     */
    private static final int RETRY_TIMES = 10;

    /**
     * 重试间隔时间（毫秒）
     */
    private static final long SLEEP_MILLIS = 500;

    /**
     * 加锁的Lua脚本（已弃用）
     * 因为新版的Redis加锁操作已经为原子性操作，所以放弃使用Lua脚本
     */
    private static final String LOCK_LUA =
            "if redis.call(\"setnx\",KEYS[1],ARGV[1]) == 1 " +
                    "then " +
                    " return redis.call('expire',KEYS[1],ARGV[2]) " +
                    "else " +
                    " return 0 " +
                    "end";

    /**
     * 释放分布式锁的Lua脚本
     * 如果redis.get(KEYS[1]) == ARGV[1],则redis delete KEYS[1]
     * 否则返回0
     * KEYS[1] , ARGV[1] 是参数，我们调用的时候传递这两个参数
     * KEYS[1] 主要用来传递在Redis中用作key值的参数
     * ARGV[1] 主要用来传递在Redis中用做value值的参数
     */
    private static final String UNLOCK_LUA =
            "if redis.call(\"get\",KEYS[1]) == ARGV[1] "
                    + "then "
                    + " return redis.call(\"del\",KEYS[1]) "
                    + "else "
                    + " return 0 "
                    + "end ";

    /**
     * 检查Redis键是否被锁定
     *
     * @param redisKey Redis键
     * @param value 锁的值
     * @return 是否成功获取锁
     */
    public Boolean isLock(String redisKey, String value) {
        return lock(redisKey, value, RETRY_TIMES);
    }

    /**
     * 获取分布式锁（带重试机制）
     *
     * @param redisKey Redis键
     * @param value 锁的值
     * @param retryTimes 重试次数
     * @return 是否成功获取锁
     */
    public Boolean lock(String redisKey, String value, int retryTimes) {
        boolean result = lockKey(redisKey, value);

        // 如果获取锁失败，进行重试
        while (!(result) && retryTimes-- > 0) {
            try {
                log.debug("lock failed, retrying...{}", retryTimes);
                Thread.sleep(RedisDistributedLock.SLEEP_MILLIS);
            } catch (InterruptedException e) {
                return false;
            }
            result = lockKey(redisKey, value);
        }

        return result;
    }

    /**
     * 获取分布式锁（原子操作）
     *
     * @param key Redis键
     * @param value 锁的值
     * @return 是否成功获取锁
     */
    public Boolean lockKey(final String key, final String value) {
        try {
            RedisCallback<Boolean> callback = (connection) -> connection.set(
                    key.getBytes(StandardCharsets.UTF_8),
                    value.getBytes(StandardCharsets.UTF_8),
                    Expiration.milliseconds(RedisDistributedLock.TIMEOUT_MILLIS),
                    RedisStringCommands.SetOption.SET_IF_ABSENT
            );

            return template.execute(callback);
        } catch (Exception e) {
            log.info("lock key fail because of ", e);
        }

        return false;
    }

    /**
     * 获取分布式锁（使用键值作为值）
     *
     * @param keyValue Redis键（同时作为值）
     * @return 是否成功获取锁
     */
    public Boolean lockKey(final String keyValue) {
        try {
            RedisCallback<Boolean> callback = (connection) -> connection.set(
                    keyValue.getBytes(StandardCharsets.UTF_8),
                    keyValue.getBytes(StandardCharsets.UTF_8),
                    Expiration.milliseconds(RedisDistributedLock.TIMEOUT_MILLIS),
                    RedisStringCommands.SetOption.SET_IF_ABSENT
            );

            return template.execute(callback);
        } catch (Exception e) {
            log.info("lock key fail because of ", e);
        }

        return false;
    }

    /**
     * 释放分布式锁资源
     *
     * @param redisKey Redis键
     * @param value 锁的值
     * @return 是否成功释放锁
     */
    public Boolean releaseLock(String redisKey, String value) {
        try {
            RedisCallback<Boolean> callback = (connection) -> connection.eval(
                    UNLOCK_LUA.getBytes(),
                    ReturnType.BOOLEAN,
                    1,
                    redisKey.getBytes(StandardCharsets.UTF_8),
                    value.getBytes(StandardCharsets.UTF_8)
            );

            return template.execute(callback);
        } catch (Exception e) {
            log.info("release lock fail because of ", e);
        }

        return false;
    }

    /**
     * 释放分布式锁资源（使用键值作为值）
     *
     * @param redisKeyValue Redis键（同时作为值）
     * @return 是否成功释放锁
     */
    public Boolean releaseLock(String redisKeyValue) {
        try {
            RedisCallback<Boolean> callback = (connection) -> connection.eval(
                    UNLOCK_LUA.getBytes(),
                    ReturnType.BOOLEAN,
                    1,
                    redisKeyValue.getBytes(StandardCharsets.UTF_8),
                    redisKeyValue.getBytes(StandardCharsets.UTF_8)
            );

            return template.execute(callback);
        } catch (Exception e) {
            log.info("release lock fail because of ", e);
        }

        return false;
    }
}
